## -*- coding: utf-8 -*-
'''
Created on 12/giu/2009

@author: sax
'''
# pylint: disable-msg=W0212


from django.core.urlresolvers import reverse, resolve, NoReverseMatch
from django.utils.functional import update_wrapper
from django.db import models
from django.utils.translation import ugettext as _
from django.contrib import  admin
from django.http import HttpResponse, HttpResponseRedirect
from xadmin.ajax.service import AjaxService
from xadmin.ajax import DatePickerWidget
from xadmin.widgets import ReadOnlyWidget, ModelLinkWidget, URLWidget, LabelWidget, HiddenValueWidget
from django.utils.encoding import force_unicode
import logging
import filterspecs #@UnusedImport
from xadmin.ajax.widgets.autocomplete import AjaxLookupInput
from xadmin.xbuttons import XModelAdminButton
from django.core.exceptions import PermissionDenied
from xadmin.util import unquote

LOGGER = logging.getLogger('xadmin.xamodeladmin')


from django.template.context import Context
from django.template.loader import get_template
from django.utils.safestring import mark_safe


class ROField(models.Field):
    pass

XFORMFIELD_FOR_DBFIELD_DEFAULTS = {
                                   models.URLField:    {'widget': URLWidget},
                                   models.DateField:   {'widget': DatePickerWidget},
                                   ROField:    {'widget': ReadOnlyWidget},
                                   }


class XModelAdmin(admin.ModelAdmin):
    disabled = []
    read_only_fields = []
    model_link_fields = []
    read_only = False
    read_only_inlines = []
    buttons = []
    services = []
    fk_service = None
    search_help_text = None
    filters_mapping = {}

    verbose_name_plural = None

    _MODE_CHANGE = 1
    _MODE_ADD = 2
    _MODE_LIST = 3
    _MODE_UNKNOW = 4

    def __init__(self, model, admin_site):

        super(XModelAdmin, self).__init__(model, admin_site)

        class OptionEnhClass(self.opts.__class__):
            def get_read_permission(self):
                return 'read_%s' % self.object_name.lower()

            def get_read_field_restriction(self, field_name):
                return 'cant_read_%s' % field_name

            def get_edit_field_restriction(self, field_name):
                return 'cant_edit_%s' % field_name

        self.opts.__class__ = OptionEnhClass

        self.formfield_overrides.update(XFORMFIELD_FOR_DBFIELD_DEFAULTS)

        self.__mode = XModelAdmin._MODE_UNKNOW
        self._cached_form = None

        self._services = {} # dict of ajax service: fieldname: AjaxService
        self.fk_service_viewname = None
        if self.verbose_name_plural is None:
            self.verbose_name_plural = self.model._meta.verbose_name_plural

        self.setup_service_for_foreign_key()
        self._update_inlines()


    def _update_inlines(self):
        for i in self.inlines:
            setattr(i, "read_only", (i in self.read_only_inlines))

    def setup_service_for_foreign_key(self):
        LOGGER.debug(self.model)
        self.fk_service_viewname = '%s_%s_service_fk' % (self.model._meta.app_label, self.model._meta.module_name)
        if isinstance(self.fk_service, AjaxService):
            self._services['fk'] = (self.fk_service_viewname, self.fk_service)

        elif isinstance(self.fk_service, (list, tuple)) and isinstance(self.fk_service[0], (list, tuple)):
            self.fk_service[0].insert(0, 'pk')
            self._services['fk'] = (self.fk_service_viewname,
                                     AjaxService(self.model.objects.all(), self.fk_service[0], self.fk_service[1])
                                     )

        elif bool(self.fk_service):
            ret_fields = ['pk']
            criteria = []
            for f in self.fk_service:
                ret_fields.append(f)
                criteria.append('%s__istartswith' % f)
            self._services['fk'] = (self.fk_service_viewname,
                                     AjaxService(self.model.objects.all(), ret_fields, criteria)
                                     )

    def formfield_for_dbfield(self, db_field, **kwargs):
        LOGGER.debug(db_field.name)

#        if db_field.name in self.read_only_fields:
#            db_field.__class__ = ROField

        ret = super(XModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)

#        if db_field.name in self.read_only_fields:
#            db_field.widget = HiddenValueWidget

        return ret

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        """
        Get a form Field for a ForeignKey.
        """
        LOGGER.debug(db_field)
        ret = super(XModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

        fk_model_admin = self.admin_site._registry.get(db_field.rel.to)

        # if foreign key is registered in admin and is XModelAdmin
        if fk_model_admin and hasattr(fk_model_admin, 'fk_service'):
            if bool(fk_model_admin.fk_service):
                view_name = "%s:%s" % (self.admin_site.name, fk_model_admin.fk_service_viewname)
                ret.widget = AjaxLookupInput(view_name)

        return ret

#    def _get_model_change_link(self, model_instance):
#        LOGGER.debug(model_instance)
#        info = self.admin_site.name, model_instance._meta.app_label, model_instance._meta.module_name #IGNORE:W0212
#        name = '%s:%s_%s_change' % info
#        return reverse(name, args=[model_instance.pk])
#
#    def _get_model_change_url(self, model_instance, label=None):
#        LOGGER.debug(model_instance)
#        lbl = label or str(model_instance)
#        url = self._get_model_change_link(model_instance)
#        ret = '<a class="modellink" href="%s">%s</a>' % (url, lbl)
#        return ret
    
#    def _get_form_options(self, request, obj):
#        LOGGER.debug( obj )
#        kwargs = {}
##        kwargs['exclude'] = []
##        if request.method == 'POST' and self.__mode == XModelAdmin._MODE_CHANGE:            
##            kwargs['exclude'] =  self.read_only_fields             
##            kwargs['exclude'].extend( self.model_link_fields )
#         
#        return kwargs


    def _post_process_form(self, request, form, obj):
        LOGGER.debug("read_only:%s mode:%s " % (self.read_only, self.__mode))
        if not self.read_only and (self.__mode == XModelAdmin._MODE_CHANGE):
            LOGGER.debug(" processing ")
            for field_name in self.read_only_fields:
                if field_name in form.base_fields and form.base_fields.get(field_name, None):
                    form.base_fields[field_name].widget = ReadOnlyWidget(obj, field_name)

            for field_name in self.model_link_fields:
                if field_name in form.base_fields:
                    form.base_fields[field_name].widget = ModelLinkWidget(self.admin_site, obj, field_name)

            for field_name in self.disabled:
                if field_name in form.base_fields:
                    form.base_fields[field_name].widget.attrs['disabled'] = 'disabled'

        for field_name in form.base_fields:
            if self.has_edit_field_restriction(request, field_name=field_name):
                form.base_fields[field_name].widget = ReadOnlyWidget(obj, field_name)
            if self.has_read_field_restriction(request, field_name=field_name):
                form.base_fields[field_name].widget = HiddenValueWidget(obj, field_name)
                #form.base_fields[field_name].label = ""

    def _process_inline_admin_formsets(self, inline_admin_formsets):
        self._update_inlines()
        for inline_admin_formset in inline_admin_formsets:
            if inline_admin_formset.opts.read_only:
                self._set_formset_read_only(inline_admin_formset)

    def _set_formset_read_only(self, inline_admin_formset):
        LOGGER.debug(inline_admin_formset)
        inline_admin_formset.formset.forms = inline_admin_formset.formset.initial_forms
        inline_admin_formset.formset.can_delete = False
        for form in inline_admin_formset.formset.forms:
            for fld in form.visible_fields():
                fld.field.widget = LabelWidget()

    def get_form(self, request, obj=None, **kwargs): #IGNORE:W0613
        LOGGER.debug(obj)

#        options = self._get_form_options(request, obj)
        form = super(XModelAdmin, self).get_form(request, obj)

        self._post_process_form(request, form, obj)

        return form

    def get_formsets(self, request, obj=None):
        LOGGER.debug("obj: %s" % obj)

        if request.method == 'POST':
            # we need to remove ro inlines
            for inline in self.inline_instances:
                if  not inline.read_only:
                    yield inline.get_formset(request, obj)
        else:
            for inline in self.inline_instances:
                yield inline.get_formset(request, obj)

    def _get_default_context(self, request, extra_context, object_id=None):
        LOGGER.debug("%s" % extra_context)


        t = request.GET.get('_tab', 0)
        ctx = {'inline_collapseable': False,
                'inline_orderable': False,
                'has_read_permission': self.has_read_permission(request, None),
                'read_only': self.read_only,
                'selected_tab': t,
                'buttons' : self._get_extra_buttons(request, object_id)
                }
        for i in self.inline_instances:
#            if hasattr(i, 'orderable') and i.orderable:
#                ctx['inline_orderable'] = True
            if hasattr(i, 'collapseable') and i.collapseable:
                ctx['inline_collapseable'] = True


            i.initial_collapsed = getattr(i, 'initial_collapsed', False)


        ctx.update(extra_context or {})
        return ctx

    def _process_change_form_context(self, request, context):
        return context

    def _get_object_by_pk(self, object_id):
        model = self.model
        opts = model._meta

        try:
            obj = self.queryset(None).get(pk=unquote(object_id))
        except model.DoesNotExist:
            # Don't raise Http404 just yet, because we haven't checked
            # permissions yet. We don't want an unauthenticated user to be able
            # to determine whether a given object exists.
            obj = None
        
        return obj

    def add_view(self, request, form_url='', extra_context=None):
        LOGGER.debug("%s" % extra_context)
        self.__mode = XModelAdmin._MODE_ADD
        extra_context = self._get_default_context(request, extra_context)
        return super(XModelAdmin, self).add_view(request, form_url, extra_context)

    def change_view(self, request, object_id, extra_context=None):
        LOGGER.debug("object_id:%s extra_context:%s " % (object_id, extra_context))

        self.__mode = XModelAdmin._MODE_CHANGE
        extra_context = self._get_default_context(request, extra_context, object_id)

        return super(XModelAdmin, self).change_view(request, object_id, extra_context)

    def read_view(self, request, object_id, extra_context=None):
        obj = self._get_object_by_pk(object_id)
        
        if not self.has_read_permission(request, obj):
            raise PermissionDenied

        self.read_only = True
        return self.change_view(request, object_id, extra_context)
    
    def history_view(self, request, object_id, extra_context=None):
        obj = self._get_object_by_pk(object_id)
        if not self.has_read_permission(request, obj):
            raise PermissionDenied
                
        return super(XModelAdmin, self).history_view(request, object_id, extra_context)
    
    def changelist_view(self, request, extra_context=None):
        LOGGER.debug("extra_context: %s" % extra_context)
        
        self.__mode = XModelAdmin._MODE_LIST
        extra_context = self._get_default_context(request, extra_context)
        extra_context['search_help_text'] = self._get_search_help_text()

        return super(XModelAdmin, self).changelist_view(request, extra_context)


    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
        LOGGER.debug("%s %s" % (context, obj))
        context['read_only'] = self.read_only
        change = change and not self.read_only
        if self.read_only and (self.__mode == XModelAdmin._MODE_CHANGE):
            for perm in ('has_delete_permission', 'has_change_permission'):
                context[perm] = False

            obj = context['original']
            for fld in context['adminform'].form.visible_fields():
                fld.field.widget = ReadOnlyWidget(obj, fld.name)


        self._process_change_form_context(request, context)
        self._process_inline_admin_formsets(context['inline_admin_formsets'])


        return super(XModelAdmin, self).render_change_form(request, context, add, change, form_url, obj)



    def response_change(self, request, obj):
        LOGGER.debug("%s" % obj)
        opts = obj._meta

        msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
        if request.POST.has_key("_continue"):
            self.message_user(request, msg + ' ' + _("You may edit it again below."))
            if request.REQUEST.has_key('_popup'):
                return HttpResponseRedirect(request.path + "?_popup=1")
            else:
                return HttpResponseRedirect(request.path + "?_tab=" + request.REQUEST.get('selected_tab', "0"))
        return super(XModelAdmin, self).response_change(request, obj)

    def _get_search_help_text(self):
        LOGGER.debug(self.search_help_text)
        if self.search_help_text is None:
            lbls = []
            for ffilter in self.search_fields:
                f1 = ffilter.rsplit("__")
                if len(f1) > 1:
                    i = "-".join(f1[1:])
                    lbl = "%s (%s)" % (self.model._meta.get_field_by_name(f1[0])[0].verbose_name, i)
                else:
                    lbl = self.model._meta.get_field_by_name(f1[0])[0].verbose_name
                lbls.append(unicode(lbl))
            help_text = force_unicode(", ".join(lbls))
        else:
            help_text = self.search_help_text

        return help_text



    def _get_extra_buttons(self, request, object_id=None):
        LOGGER.debug("")
        btns = []
        for btn in self.buttons:
            logging.debug("parsing button %s" % btn )
            if isinstance(btn, XModelAdminButton):
                btns.append(btn.config(request, object_id, self))
            elif callable(btn):
                b = XModelAdminButton(named_url=btn.named_url)
                c = b.config(request, object_id, self)
                btns.append( c )
            elif isinstance(btn, basestring):
                b = XModelAdminButton(named_url=btn)
                btns.append(b.config(request, object_id, self))
            else:
                raise Exception("Invalid item  for buttons")

        return btns

    def _wrap_view(self, view):
        def wrapper(*args, **kwargs):
            return self.admin_site.admin_view(view)(*args, **kwargs)
        return update_wrapper(wrapper, view)

    def _get_view_name(self, action):
        info = self.model._meta.app_label, self.model._meta.module_name
        return "%s_%s" % ('%s_%s' % info, action)

    def get_listview_name(self):
        return self._get_view_name('changelist')

    def get_addview_name(self):
        return self._get_view_name('add')

    def get_historyview_name(self):
        return self._get_view_name('history')

    def get_deleteview_name(self):
        return self._get_view_name('delete')

    def get_readview_name(self):
        return self._get_view_name('read')

    def get_changeview_name(self):
        return self._get_view_name('change')

    def get_services_urls(self):
        LOGGER.debug(self.model)
        from django.conf.urls.defaults import patterns, url
        ret = []
        for fieldname, service in self._services.items():
            LOGGER.info("created service %s for %s" % (fieldname, service[0]))
            ret.append(url(r'^service/%s/$' % fieldname,
                                self._wrap_view(service[1]),
                                name=service[0]
                                ),
                        )

        return patterns('', *ret)


    def get_urls(self):
        LOGGER.debug(self.model)
        from django.conf.urls.defaults import patterns, url
        from xadmin.utils import has_all_attrs
        info = self.model._meta.app_label, self.model._meta.module_name

        ret = self.get_services_urls()

        import inspect
        url_buttons = []
        for btn in self.buttons:
            logging.debug( btn )
            if callable(btn) :
                args, varargs, varkw, defaults = inspect.getargspec(btn)
                name = self._get_view_name(btn.__name__)
                btn.named_url = name
                uri = r'^%s/%s$' % (btn.__name__, "/".join([ "(?P<%s>.*)" % a for a in args[1:] ]))
                logging.debug("Created url for button: %s %s" % (uri, name) )
                url_buttons.append(
                            url(uri,
                            self._wrap_view(btn),
                            {'model_admin': self},
                            name)
                        )
             
                 
                 
        ret += patterns('', *url_buttons)

        ret += patterns('',
            url(r'^$',
                self._wrap_view(self.changelist_view),
                name=self.get_listview_name()),
            url(r'^add/$',
                self._wrap_view(self.add_view),
                name=self.get_addview_name()),
            url(r'^(.+)/history/$',
                self._wrap_view(self.history_view),
                name=self.get_historyview_name()),
            url(r'^(.+)/delete/$',
                self._wrap_view(self.delete_view),
                name=self.get_deleteview_name()),
            url(r'^(.+)/read/$',
                self._wrap_view(self.read_view),
                name=self.get_readview_name()),
            url(r'^(.+)/$',
                self._wrap_view(self.change_view),
                name=self.get_changeview_name()),
        )

        return ret




    def has_read_permission(self, request, obj=None): #IGNORE:W0613
        """
        Returns True if the given request has permission to change the given
        Django model instance.

        If `obj` is None, this should return True if the given request has
        permission to change *any* object of the given type.
        """
        LOGGER.debug("%s" % obj)
        opts = self.opts
        return request.user.has_perm(opts.app_label + '.' + opts.get_read_permission())

#    def has_change_permission(self, request, obj=None):
#        if self.__mode == XModelAdmin._MODE_LIST:
#            return self.has_view_permission(request)
#        return (not self.read_only) and super(XModelAdmin, self).has_change_permission(request)

    def has_delete_permission(self, request, obj=None):
        LOGGER.debug("%s" % obj)
        return (not self.read_only) and super(XModelAdmin, self).has_delete_permission(request)
#
#    
    def has_edit_field_restriction(self, request=None, obj=None, field_name=None):
        if request.user.is_superuser:
            return False

        opts = self.opts
        perm_name = opts.get_edit_field_restriction(field_name)
        perm_value = request.user.has_perm(opts.app_label + '.' + perm_name)

        LOGGER.debug("%s.%s : %s (%s) " % (obj, field_name, perm_name, perm_value))
        return perm_value

    def has_read_field_restriction(self, request=None, obj=None, field_name=None):
        if request.user.is_superuser:
            return False
        opts = self.opts
        perm_name = opts.get_read_field_restriction(field_name)
        perm_value = request.user.has_perm(opts.app_label + '.' + perm_name)

        LOGGER.debug("%s.%s : %s (%s) " % (obj, field_name, perm_name, perm_value))
        return perm_value

        #return request.user.has_perm(opts.app_label + '.' + opts.has_edit_field_restriction(field_name) )
#
#        return (not self.read_only) and super(XModelAdmin, self).has_delete_permission(request)
#
#    def has_read_field_restriction(self, request, obj=None, field_name=None):
#        return (not self.read_only) and super(XModelAdmin, self).has_delete_permission(request)

#    @classmethod
#    def _wrap_column(cls, f, field_name, label=None, order=None):
#        """ utility to add extra attributes to method that could be used as columns """
#        f.short_description = label or field_name
#        f.allow_tags = True
#        f.admin_order_field = order
#        return f


#    @classmethod
#    def get_model_change_href(cls, field_name, label=None, order=None):
#
#        def inner(self, obj):
#            model_instance = getattr(obj, field_name)
#            return self._get_model_change_url(model_instance) #IGNORE:W0212
#        return cls._wrap_column(inner, field_name, label, order)

#    @classmethod
#    def get_email_href(cls, field_name, label=None, order=None): 
#        def inner(cls, obj):  #IGNORE:W0613
#            value = getattr(obj, field_name)
#            if not value:
#                return ""
#            lbl = label or str(value)
#            return "<a href='mailto:%s'>%s</a>" % (value, lbl)
#
#        return cls._wrap_column(inner, field_name, label, order)


ModelAdmin = XModelAdmin
site = admin.site
